PackerとCodeBuildでAuto Scalingの起動設定を更新してみる
はじめに
おはようございます、もきゅりんです。
あけましておめでとうございます。
前回 はMackerelエージェント導入済みAMIをPackerで作成しました。
このAMI、ゴールデンイメージとなれば Auto Scalingにはすぐ反映したい ですよねえ。
Auto Scalingの起動設定の切り替えは地味にめんどくさい作業です。
ということで、CodeCommit -> CodeBuild -> 起動設定の作成 -> Auto Scalingに起動設定の反映 をしてみました。
以下のような図となります。
かなり古い記事ですが、下記を参考にして進めていきます。
AWS CodeBuild と HashiCorp Packer を用いた AMI ビルダーの構築方法
前提条件
- 利用できるAuto Scaling環境があること
- CodeCommitが利用できる状態であること
- IAM権限を設定・更新できること
CodeCommitリポジトリの作成については、『CodeCommit ユーザーガイド』の「セットアップ」を参照してください。
CodeCommitには前回で利用したPackerのTemplateが保存されているとします。
CodeBuildがビルドするのに、mfaの記述は必要ないので削除しておきましょう。
# packer-apache-mackerel.json { "builders": [ { "type": "amazon-ebs", "ami_name": "apache-mackerel", "region": "ap-northeast-1", "source_ami_filter": { "filters": { "name": "amzn2-ami-hvm-*-x86_64-gp2" }, "owners": ["137112412989"], "most_recent": true }, "instance_type": "t2.micro", "ssh_username": "ec2-user" } ], "provisioners": [ { "type": "shell", "inline": [ "sudo yum -y update", "sudo yum -y install httpd", "sudo systemctl start httpd && sudo systemctl enable httpd", "curl -fsSL https://mackerel.io/file/script/amznlinux/setup-all-yum-v2.sh | MACKEREL_APIKEY='YOUR_MACKEREL_APIKEY' sh", "sudo sed -i -e '/^#\\s*\\[host_status\\]/s/^#\\s*//' /etc/mackerel-agent/mackerel-agent.conf", "sudo sed -i -e '/^#\\s*on_start\\s*=\\s*\"working\"/s/^#\\s*//' /etc/mackerel-agent/mackerel-agent.conf", "sudo sed -i -e '/^#\\s*AUTO_RETIREMENT\\s*=\\s*1/s/^#\\s*//' /etc/sysconfig/mackerel-agent" ] } ] }
やること
- コンソールで AWS CodeBuild のプロジェクトを作成する
- ビルドスペックを作成する
- AWS CodeBuild プロジェクトを実行する
- CodePipelineを作成する
1. コンソールで AWS CodeBuild のプロジェクトを作成する
AWS CodeBuild のコンソールからプロジェクトを開始します。
適当な名前を入れます。
今回のソースは CodeCommit
です。
ブランチを選択します。
記事では Ubuntu コンテナを利用していますが、ここでは Amazon Linux2 のコンテナを利用しています。(特に理由はないです。)
新しいサービスロールを作成していますが、既存のロールが環境にあればそれを使っても支障ありません。
後ほど権限を追加するため、利用するロール名は控えておきます。
その他はデフォルト設定で問題で進めます。
控えたIAMロールを検索してPackerに必要となる新しいポリシーを適当な名前で追加します。
(e.g. codebuild-AMI_Builder-ec2-permissions)
Amazon AMI Builder / IAM Task or Instance Role
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action" : [ "ec2:AttachVolume", "ec2:AuthorizeSecurityGroupIngress", "ec2:CopyImage", "ec2:CreateImage", "ec2:CreateKeypair", "ec2:CreateSecurityGroup", "ec2:CreateSnapshot", "ec2:CreateTags", "ec2:CreateVolume", "ec2:DeleteKeyPair", "ec2:DeleteSecurityGroup", "ec2:DeleteSnapshot", "ec2:DeleteVolume", "ec2:DeregisterImage", "ec2:DescribeImageAttribute", "ec2:DescribeImages", "ec2:DescribeInstances", "ec2:DescribeInstanceStatus", "ec2:DescribeRegions", "ec2:DescribeSecurityGroups", "ec2:DescribeSnapshots", "ec2:DescribeSubnets", "ec2:DescribeTags", "ec2:DescribeVolumes", "ec2:DetachVolume", "ec2:GetPasswordData", "ec2:ModifyImageAttribute", "ec2:ModifyInstanceAttribute", "ec2:ModifySnapshotAttribute", "ec2:RegisterImage", "ec2:RunInstances", "ec2:StopInstances", "ec2:TerminateInstances" ], "Resource" : "*" }] }
ポリシーを作成したら、ロールにアタッチします。
さらにもう1つ、ASGのポリシーも必要です。
今回は若干雑な処置ですが、AutoScalingFullAccess
をアタッチしてしまいます。
2. ビルドスペックを作成する
ビルドで必要な対応を buildspec.yml
に記載します。
やっていることは、PackerでAMIを生成し、起動設定を作成し、指定されたAuto Scaling Group(以下ASG)の起動設定を更新し、現在使用している起動設定を破棄します。
過去の起動設定を破棄したくない場合や各環境変数は環境に応じて編集して下さい。
なお、どこかでコケてもロールバックを行うような処理は考慮されていません。
Packerが生成するAMIのIDを取得するのにこちらを参考としました。
version: 0.2 env: variables: ENV: 'dev' NameTagPrefix: 'ProjectName' SG: 'SECURITY_GROUP_ID' InstanceType: 'INSTANCE_TYPE' VolumeSize: 8 VolumeType: 'gp2' KeyName: 'SECRET_KEY_NAME' ASGName: 'AUTO_SCALING_GROUP_NAME' phases: install: runtime-versions: docker: 18 pre_build: commands: - echo "HashiCorp Packer をインストール中..." - curl -qL -o packer.zip https://releases.hashicorp.com/packer/0.12.3/packer_0.12.3_linux_amd64.zip && unzip packer.zip - echo "jq をインストール中..." - curl -qL -o jq https://stedolan.github.io/jq/download/linux64/jq && chmod +x ./jq - echo "packer-apache-mackerel.json をバリデーションします" - ./packer validate packer-apache-mackerel.json build: commands: ### HashiCorp Packer cannot currently obtain the AWS CodeBuild-assigned role and its credentials ### Manually capture and configure the AWS CLI to provide HashiCorp Packer with AWS credentials ### More info here: https://github.com/mitchellh/packer/issues/4279 - echo "AWS credentials を設定" - curl -qL -o aws_credentials.json http://169.254.170.2/$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > aws_credentials.json - aws configure set region $AWS_REGION - aws configure set aws_access_key_id `./jq -r '.AccessKeyId' aws_credentials.json` - aws configure set aws_secret_access_key `./jq -r '.SecretAccessKey' aws_credentials.json` - aws configure set aws_session_token `./jq -r '.Token' aws_credentials.json` - echo "HashiCorp Packer のテンプレート amazon-linux_packer-template.json をビルド" - AMIID=`./packer build -machine-readable packer-apache-mackerel.json | awk -F , '$3 ~ /^artifact$/ && $5 ~ /^id$/ { print $6 };' | cut -d ":" -f 2` - echo $AMIID post_build: commands: - echo "HashiCorp Packer によるビルドが完了しました。 `date`" - echo "AutoScaling Launch Configuration を設定" - aws autoscaling create-launch-configuration --launch-configuration-name ${ENV}-${NameTagPrefix}-$(date +%Y%m%d%H%M) --image-id $AMIID --security-groups $SG --key-name $KeyName --instance-type $InstanceType --block-device-mappings "DeviceName=/dev/xvda,Ebs={VolumeSize=8,VolumeType=gp2}" --instance-monitoring Enabled=false - echo "AutoScaling Launch Configuration が完了しました" - echo "AutoScaling の起動設定 を更新します" - ASG=`aws autoscaling describe-auto-scaling-groups --auto-scaling-group-name ${ASGName}` - PreLaunchConfigName=`echo $ASG | jq -r .AutoScalingGroups[].LaunchConfigurationName` - aws autoscaling update-auto-scaling-group --auto-scaling-group-name $ASGName --launch-configuration-name ${ENV}-${NameTagPrefix}-$(date +%Y%m%d%H%M) - echo "AutoScaling の起動設定 を更新が完了しました" - aws autoscaling delete-launch-configuration --launch-configuration-name $PreLaunchConfigName - echo "古いAutoScaling Launch Configuration を削除しました"
3. AWS CodeBuild プロジェクトを実行する
ビルドを開始します。数分の時間がかかります。
成功です。
ASG、起動設定、AMIを確認していきます。
起動設定の名前に利用されている時間はUTCです。
そしてAMI。
特に問題なさそうですね。
ここまでの作業はご覧の通りですが、まぁなかなか面倒な作業ですよね。
ということで、CodePipelineを構築する簡易なCFnテンプレートを作りました。
4. CodePipelineを作成する
以下のテンプレートでは便宜上、SecurityGroupIDやASG名などをパラメータとして指定していますが、実際は別スタックの出力値を取得して利用されるのがよいかと思います。
aws cloudformation deploy --stack-name demo-packer-codepipeline --template-file sample.yml \ --parameter-overrides \ CodeCommitRepositoryName='DemoPacker' \ CodeBuildProjectName='Demo-CodePipeline-AMI-Builder' \ PipelineName='Demo-AMI-Builder-CodePipeline' \ ASGName='ASG_NAME' \ SecurityGroupID='SG_ID' \ InstanceType='t3.micro' \ KeyName='YOUR_KEY_NAME' \ --capabilities CAPABILITY_NAMED_IAM
適宜、適当な名前をパラメータに代入して実行して下さい。
本筋とは関係ありませんが、このなかなかの量のパラメータ数とうまく対応したり、CFnとの付き合い方を語った実益ある弊社ブログを紹介しておきます。
CloudFormationの全てを味わいつくせ!「AWSの全てをコードで管理する方法〜その理想と現実〜」
# sample.yml AWSTemplateFormatVersion: 2010-09-09 Description: CodePipeline For AMI Update with Packer # ------------------------------------------------------------# # Parameters # ------------------------------------------------------------# Parameters: ENV: Type: String Default: 'demo' NameTagPrefix: Type: String Default: 'ProjectName' CodeCommitRepositoryName: Type: String CodeBuildProjectName: Type: String PipelineName: Type: String ASGName: Type: String SecurityGroupID: Type: 'AWS::EC2::SecurityGroup::Id' InstanceType: Type: String VolumeSize: Type: Number Default: 8 KeyName: Type: 'AWS::EC2::KeyPair::KeyName' # ------------------------------------------------------------# # Resources # ------------------------------------------------------------# Resources: # S3Bucket ArtifactBucket: Type: AWS::S3::Bucket # CodeWatchEventを実行できるIAMRole AmazonCloudWatchEventRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - events.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: cwe-pipeline-execution PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: codepipeline:StartPipelineExecution Resource: !Join - '' - - 'arn:aws:codepipeline:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref 'PipelineName' # CodeBuildに適用するIAMRole CodeBuildServiceRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: codebuild.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonS3FullAccess - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess - arn:aws:iam::aws:policy/AutoScalingFullAccess Policies: - PolicyName: SampleCodeBuildAccess PolicyDocument: Version: '2012-10-17' Statement: - Resource: '*' Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - Resource: arn:aws:s3:::codepipeline-${AWS::Region}-* Effect: Allow Action: - s3:GetObject - s3:PutObject - s3:GetObjectVersion - s3:GetBucketAcl - s3:GetBucketLocation - Resource: !Sub arn:aws:codecommit:${AWS::Region}:${AWS::AccountId}:${CodeCommitRepositoryName} Effect: Allow Action: - codecommit:GitPull - Resource: !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/${CodeBuildProjectName}-* Effect: Allow Action: - codebuild:CreateReportGroup - codebuild:CreateReport - codebuild:UpdateReport - codebuild:BatchPutTestCases - Resource: '*' Effect: Allow Action: - ec2:AttachVolume - ec2:AuthorizeSecurityGroupIngress - ec2:CopyImage - ec2:CreateImage - ec2:CreateKeypair - ec2:CreateSecurityGroup - ec2:CreateSnapshot - ec2:CreateTags - ec2:CreateVolume - ec2:DeleteKeyPair - ec2:DeleteSecurityGroup - ec2:DeleteSnapshot - ec2:DeleteVolume - ec2:DeregisterImage - ec2:DescribeImageAttribute - ec2:DescribeImages - ec2:DescribeInstances - ec2:DescribeInstanceStatus - ec2:DescribeRegions - ec2:DescribeSecurityGroups - ec2:DescribeSnapshots - ec2:DescribeSubnets - ec2:DescribeTags - ec2:DescribeVolumes - ec2:DetachVolume - ec2:GetPasswordData - ec2:ModifyImageAttribute - ec2:ModifyInstanceAttribute - ec2:ModifySnapshotAttribute - ec2:RegisterImage - ec2:RunInstances - ec2:StopInstances - ec2:TerminateInstances # CodePipelineに適用するIAMRole CodePipelineServiceRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: codepipeline.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SamplePipeline PolicyDocument: Version: 2012-10-17 Statement: - Resource: - !Sub arn:aws:s3:::${ArtifactBucket}/* Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:GetObjectVersion - s3:GetBucketVersioning - Resource: '*' Effect: Allow Action: - codecommit:GetRepository - codecommit:ListBranches - codecommit:GetUploadArchiveStatus - codecommit:UploadArchive - codecommit:CancelUploadArchive - codebuild:StartBuild - codebuild:StopBuild - codebuild:BatchGet* - codebuild:Get* - codebuild:List* - codecommit:GetBranch - codecommit:GetCommit - iam:PassRole # CloudWatchEventの実行ルール AmazonCloudWatchEventRule: Type: AWS::Events::Rule Properties: EventPattern: source: - aws.codecommit detail-type: - 'CodeCommit Repository State Change' resources: - !Join - '' - - 'arn:aws:codecommit:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref 'CodeCommitRepositoryName' detail: event: - referenceCreated - referenceUpdated referenceType: - branch referenceName: - master Targets: - Arn: !Join - '' - - 'arn:aws:codepipeline:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref 'PipelineName' RoleArn: !GetAtt AmazonCloudWatchEventRole.Arn Id: codepipeline-AppPipeline # CodeBuild CodeBuildProject: Type: AWS::CodeBuild::Project Properties: Artifacts: Type: CODEPIPELINE Environment: PrivilegedMode: true ComputeType: BUILD_GENERAL1_SMALL Image: aws/codebuild/amazonlinux2-x86_64-standard:2.0 Type: LINUX_CONTAINER EnvironmentVariables: - Name: AWS_DEFAULT_REGION Value: !Ref AWS::Region - Name: ENV Value: !Ref ENV - Name: NameTagPrefix Value: !Ref NameTagPrefix - Name: SecurityGroupID Value: !Ref SecurityGroupID - Name: InstanceType Value: !Ref InstanceType - Name: VolumeSize Value: !Ref VolumeSize - Name: VolumeType Value: 'gp2' - Name: KeyName Value: !Ref KeyName - Name: ASGName Value: !Ref ASGName Name: !Ref CodeBuildProjectName ServiceRole: !Ref CodeBuildServiceRole Source: Type: CODEPIPELINE BuildSpec: | version: 0.2 phases: install: runtime-versions: docker: 18 pre_build: commands: - echo "HashiCorp Packer をインストール中..." - curl -qL -o packer.zip https://releases.hashicorp.com/packer/0.12.3/packer_0.12.3_linux_amd64.zip && unzip packer.zip - echo "jq をインストール中..." - curl -qL -o jq https://stedolan.github.io/jq/download/linux64/jq && chmod +x ./jq - echo "packer-apache-mackerel.json をバリデーションします" - ./packer validate packer-apache-mackerel.json build: commands: ### HashiCorp Packer cannot currently obtain the AWS CodeBuild-assigned role and its credentials ### Manually capture and configure the AWS CLI to provide HashiCorp Packer with AWS credentials ### More info here: https://github.com/mitchellh/packer/issues/4279 - echo "AWS credentials を設定" - curl -qL -o aws_credentials.json http://169.254.170.2/$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > aws_credentials.json - aws configure set region $AWS_REGION - aws configure set aws_access_key_id `./jq -r '.AccessKeyId' aws_credentials.json` - aws configure set aws_secret_access_key `./jq -r '.SecretAccessKey' aws_credentials.json` - aws configure set aws_session_token `./jq -r '.Token' aws_credentials.json` - echo "HashiCorp Packer のテンプレート amazon-linux_packer-template.json をビルド" - echo "HashiCorp Packer のテンプレート amazon-linux_packer-template.json をビルド" - AMIID=`./packer build -machine-readable packer-apache-mackerel.json | awk -F , '$3 ~ /^artifact$/ && $5 ~ /^id$/ { print $6 };' | cut -d ":" -f 2` - echo $AMIID post_build: commands: - echo "HashiCorp Packer によるビルドが完了しました。 `date`" - echo "AutoScaling Launch Configuration を設定" - aws autoscaling create-launch-configuration --launch-configuration-name ${ENV}-${NameTagPrefix}-$(date +%Y%m%d%H%M) --image-id $AMIID --security-groups $SG --key-name $KeyName --instance-type $InstanceType --block-device-mappings "DeviceName=/dev/xvda,Ebs={VolumeSize=8,VolumeType=gp2}" --instance-monitoring Enabled=false - echo "AutoScaling Launch Configuration が完了しました" - echo "AutoScaling の起動設定 を更新します" - ASG=`aws autoscaling describe-auto-scaling-groups --auto-scaling-group-name ${ASGName}` - PreLaunchConfigName=`echo $ASG | jq -r .AutoScalingGroups[].LaunchConfigurationName` - aws autoscaling update-auto-scaling-group --auto-scaling-group-name $ASGName --launch-configuration-name ${ENV}-${NameTagPrefix}-$(date +%Y%m%d%H%M) - echo "AutoScaling の起動設定 を更新が完了しました" - aws autoscaling delete-launch-configuration --launch-configuration-name $PreLaunchConfigName - echo "古いAutoScaling Launch Configuration を削除しました" # CodePipeLine CodePipeline: Type: AWS::CodePipeline::Pipeline Properties: RoleArn: !GetAtt CodePipelineServiceRole.Arn Name: !Ref PipelineName ArtifactStore: Type: S3 Location: !Ref ArtifactBucket Stages: - Name: Source Actions: - Name: SourceAction ActionTypeId: Category: Source Owner: AWS Version: 1 Provider: CodeCommit Configuration: RepositoryName: !Ref CodeCommitRepositoryName PollForSourceChanges: false BranchName: master RunOrder: 1 OutputArtifacts: - Name: App - Name: Build Actions: - Name: Build ActionTypeId: Category: Build Owner: AWS Version: 1 Provider: CodeBuild Configuration: ProjectName: !Ref CodeBuildProject RunOrder: 1 InputArtifacts: - Name: App # ------------------------------------------------------------# # Outputs # ------------------------------------------------------------# Outputs: PipelinelogicalID: Description: logical ID. Value: !Ref CodePipeline
成功です。
5. 最後に
実用する際はもう少々考慮、改善点がありそうですが、ソースリポジトリをCodeCommitをGitHubにしてもやることに大差ありません。
手前味噌で恐縮ですが、下記などお役に立つでしょう。
CFnでGitHub + Fargate + CodePipelineを構築してみる
以上です。
どなたかのお役に立てば幸いです。